package host.skiree.nettydemo.nio.c2;

import host.skiree.nettydemo.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

/**
 * @author gcl
 * @date 2022/9/6 10:02
 */
@Slf4j
@SpringBootTest
public class NettyDemo {

    /**
     * 用来测试
     */
    @Test
    void byteBuffer() {
        // 获取 FileChannel
        try (FileChannel fileChannel = new FileInputStream("data.txt").getChannel()) {
            // 划分ByteBuffer缓冲区,容量为10
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
            while (true) {
                // 读fileChannel的数据到byteBuffer缓冲区，返回len为-1时，无可读数据
                int len = fileChannel.read(byteBuffer);
                log.debug("实际读取到的字节数{}", len);
                // 无可读时退出
                if ( len == -1){
                    break;
                }
                // 缓冲区切换为读模式
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()){ // 是否存在未读数据
                    log.debug("实际读取到的字节{}", (char) byteBuffer.get());
                }
                // 清空缓冲区
                byteBuffer.clear();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferUtil() {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte) 0x61); // 'a'
        ByteBufferUtil.debugAll(buffer);
        buffer.put(new byte[]{0x62, 0x63, 0x64});
        ByteBufferUtil.debugAll(buffer);
//        log.debug("读取到的字节{}",buffer.get());
        buffer.flip(); // 切换到读模式
        ByteBufferUtil.debugAll(buffer);
        log.debug("读取到的字节{}",buffer.get());
        buffer.compact(); // compact清除已读数据
        ByteBufferUtil.debugAll(buffer);
        log.debug("读取到的字节{}",buffer.get());
        buffer.put(new byte[]{0x65, 0x66, 0x67});
        ByteBufferUtil.debugAll(buffer);
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferAllocate() {
        System.out.println(ByteBuffer.allocate(10).getClass()); // class java.nio.HeapByteBuffer
        System.out.println(ByteBuffer.allocateDirect(10).getClass()); // class java.nio.DirectByteBuffer
        /***
         * class java.nio.HeapByteBuffer JVM 堆内存
         *  1.读写效率底，会受到GC的影响
         * class java.nio.DirectByteBuffer JVM 直接内存
         *  1.读写效率高(少一次读写)，不会受到GC的影响
         *  2.分配内存的效率底下
         */
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferRead() {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a', 'b', 'c', 'd'});
        buffer.flip();

        // rewind 从头开始读
        buffer.get(new byte[4]);
        ByteBufferUtil.debugAll(buffer);
        buffer.rewind();
        System.out.println((char) buffer.get());
        buffer.rewind();

        // mark & reset
        // mark 是做一个标记，记录position的位置 reset是将position的位置重置到mark标记的位置
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        buffer.mark();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        buffer.reset();
        System.out.println((char) buffer.get());
        buffer.reset();
        System.out.println((char) buffer.get());
        buffer.reset();
        System.out.println((char) buffer.get());
        buffer.reset();
        System.out.println((char) buffer.get());

        // get(i) 方法不会改变position的位置，只是当前使用一次
        System.out.println((char) buffer.get(3));
        ByteBufferUtil.debugAll(buffer);
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferString() {
        // 字符串转bytebuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);
        buffer.put( "hello".getBytes() );
        ByteBufferUtil.debugAll(buffer);

        // Charset (自动切换到读模式)
        ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("你好");
        ByteBufferUtil.debugAll( byteBuffer );

        // wrap (自动切换到读模式)
        ByteBuffer byteBuffer2 = ByteBuffer.wrap("hello".getBytes());
        ByteBufferUtil.debugAll( byteBuffer2 );


        // 转String (需要将bytebuffer先切换到读模式)
        buffer.flip();
        String str = StandardCharsets.UTF_8.decode(buffer).toString();
        System.out.println(str);

        String str2 = StandardCharsets.UTF_8.decode(byteBuffer).toString();
        System.out.println(str2);

        String str3 = StandardCharsets.UTF_8.decode(byteBuffer2).toString();
        System.out.println(str3);
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferScatteringReads() {
        // 读取数据时将数据填充至多个 buffer
        try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {
            ByteBuffer b1 = ByteBuffer.allocate(3);
            ByteBuffer b2 = ByteBuffer.allocate(3);
            ByteBuffer b3 = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{b1, b2, b3});
            b1.flip();
            b2.flip();
            b3.flip();
            ByteBufferUtil.debugAll(b1);
            ByteBufferUtil.debugAll(b2);
            ByteBufferUtil.debugAll(b3);
        }catch (IOException e){
        }
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferGatheringWrites() {
        ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
        ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
        ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
        try (FileChannel channel = new RandomAccessFile("words2.txt", "rw").getChannel()) {
            channel.write(new ByteBuffer[]{b1, b2, b3});
        }catch (IOException e){
        }
    }

    /**
     * 用来测试
     */
    @Test
    void byteBufferTest() {
        /***
         * 网络上有多条数据发送给服务端，数据之间使用 \n 进行分隔
         * 但由于某种原因这些数据在接收时，被进行了重新组合，例如原始数据有3条为
         *
         *  Hello,world\n
         *  I'm zhangsan\n
         *  How are you?\n
         *
         * 变成了下面的两个 byteBuffer (黏包，半包)
         *
         *  Hello,world\nI'm zhangsan\nHo
         *  w are you?\n
         *
         * 现在要求你编写程序，将错乱的数据恢复成原始的按 \n 分隔的数据
         */
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split(buffer);
        buffer.put("w are you?\n".getBytes());
        split(buffer);
    }

    private void split(ByteBuffer buffer){
        buffer.flip(); // 切换读模式
        for (int i = 0 ; i < buffer.limit() ; i++ ){
            if ( buffer.get(i) == '\n' ){
                int len = i + 1 - buffer.position();
                ByteBuffer target = ByteBuffer.allocate(len);
                for (int j = 0 ; j < len; j++){
                    target.put(buffer.get());
                }
                ByteBufferUtil.debugAll(target);
            }
        }
        buffer.compact();// 清除已读内容
    }

}
